//
// Created by 12992 on 24-10-14.
//

// You may need to build the project (run Qt uic code generator) to get "ui_MainWindow.h" resolved

#include "mainwindow.h"
#include "ui_MainWindow.h"
#include "widgets/fileselector.h"
#include "widgets/videopreview.h"
#include "widgets/IconButton.h"
#include "widgets/audioencodeconfigitem.h"
#include <QMessageBox>
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
#include <QFileDialog>
#include <QFile>

#include "general_demuxer.h"
#include "general_muxer.h"


extern "C"
{
#include <libavutil/frame.h>
}

namespace gui
{
    using namespace std;

    MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        this->init_widgets();
        this->init_signals();
        this->read_config();
    }

    MainWindow::~MainWindow()
    {
        delete ui;
    }

    void MainWindow::init_widgets()
    {
        this->fileSelector = new widgets::FileSelector(this);
        this->ui->stackedWidget->addWidget(fileSelector);
        this->ui->stackedWidget->setCurrentIndex(1);
        this->init_tool_bar();
        this->init_metadata_widget_labels();
        this->init_video_preview_widget();
        this->init_video_encode_config_widget();
        this->init_transcoder_queue();
    }

    void MainWindow::init_signals()
    {
        connect(this->fileSelector, &widgets::FileSelector::canceled,
                this, &MainWindow::cancelSelectFile);
        connect(this->fileSelector, &widgets::FileSelector::fileUpload,
                this, &MainWindow::openMediaFile);
        connect(this->ui->actionopen, &QAction::triggered,
                this, &MainWindow::showOpenView);
        connect(this->openFileButton, &widgets::IconButton::clicked,
                this, &MainWindow::showOpenView);
        connect(this->ui->outputButton, &QPushButton::released,
                this, &MainWindow::setOutputPath);
        connect(this->ui->stackedWidget, &QStackedWidget::currentChanged,
                this, &MainWindow::showToolBar);
        connect(this->ui->actionstart, &QAction::triggered,
                this, &MainWindow::startTranscode);
        connect(this->startTranscodeButton, &widgets::IconButton::clicked,
                this, &MainWindow::startTranscode);
        connect(this->insertQueueButton, &widgets::IconButton::clicked,
                this, &MainWindow::addTranscodeQueue);
        connect(this->transcoderQueueWidget, &widgets::TranscoderQueueWidget::errorOccurred,
                this, &MainWindow::showTranscodeError);
    }

    void MainWindow::cancelSelectFile()
    {
        qDebug() << "cancel select file";
        this->ui->stackedWidget->setCurrentIndex(0);
    }

    void MainWindow::init_metadata_widget_labels()
    {
        this->set_metadata_widget_labels("没有源", "", "没有轨道", "没有轨道", "没有源");
    }

    void MainWindow::openMediaFile(const QString& path)
    {
        using namespace std;
        if (path.isEmpty())
        {
            return;
        }
#ifdef _WIN32
        string filepath = path.toStdString();
        transform(filepath.begin(), filepath.end(), filepath.begin(),
                  [](char c)
                  {
                      if (c == '/')
                      {
                          return '\\';
                      }
                      return c;
                  });
#else
        string filepath = path.toStdString();
#endif

        if (this->metadata)
        {
            qDebug() << "run clean metadata";
            this->clean_prev_metadata();
        }

        this->metadata = make_unique<utils::video_meta>(filepath);
        auto videoMainInfo =
                QString::asprintf("%s, %s, %.3lf mbps",
                                  this->metadata->get_video_encoder_type().c_str(),
                                  this->metadata->get_video_color_space().c_str(),
                                  static_cast<double>(this->metadata->get_video_avg_bitrate()) / 1000000);

        auto audioMainInfo =
                QString::asprintf("%s, %d, %s",
                                  this->metadata->get_audio_encoder_type().c_str(),
                                  this->metadata->get_audio_sample_rate(),
                                  this->metadata->get_audio_channel_layout().c_str());

        auto [width, height] = this->metadata->get_video_resolution();
        auto sizeInfo =
                QString::asprintf("%dx%d, %d fps",
                                  width, height, this->metadata->get_video_framerate());

        this->set_metadata_widget_labels(
            QString::fromStdString(this->metadata->get_video_format()),
            QString::fromStdString(this->metadata->get_video_format2()),
            videoMainInfo, audioMainInfo, sizeInfo);
        this->set_file_info_widget_labels(path);
        this->set_video_preview_widget();
        this->set_audio_encode_config_widget(this->currPreset->second);
        this->set_output(path);
        this->videoEncodeConfig->setConfig(this->currPreset->first);
        this->ui->stackedWidget->setCurrentIndex(0);
    }

    void MainWindow::showOpenView()
    {
        this->ui->stackedWidget->setCurrentIndex(1);
    }

    void MainWindow::set_metadata_widget_labels(const QString& formatInfo, const QString& formatInfo2,
                                                const QString& mainVideoInfo, const QString& mainAudioInfo,
                                                const QString& sizeInfo)
    {
        this->ui->formatInfo->setText(formatInfo);
        this->ui->formatInfo2->setText(formatInfo2);
        this->ui->mainAudioInfo->setText(mainAudioInfo);
        this->ui->mainVideoInfo->setText(mainVideoInfo);
        this->ui->sizeInfo->setText(sizeInfo);
    }

    QString second_to_time_string(int seconds)
    {
        int hours = seconds / 3600;
        seconds %= 3600;
        int minutes = seconds / 60;
        seconds %= 60;
        return QString::asprintf("%02d:%02d:%02d", hours, minutes, seconds);
    }

    void MainWindow::set_file_info_widget_labels(const QString& path)
    {
        using namespace std;
        QFileInfo fileInfo(path);
        auto last_write = fileInfo.lastModified();
        auto filesize = fileInfo.size();
        int duration = this->metadata->get_duration();
        this->ui->pathInfo->setText(path);
        this->ui->filesizeInfo->setText(QString::asprintf("%.3lf", static_cast<double>(filesize) / 1024 / 1024) + "MB");
        this->ui->lastWriteInfo->setText(last_write.toString());
        this->ui->durationInfo->setText(second_to_time_string(duration));
    }

    QVector<QImage> frame_to_image(std::vector<utils::media_frame>& frames)
    {
        QVector<QImage> vec;
        for (auto& frame: frames)
        {
            auto* image_buffer = new uchar[frame.get_frame()->height * frame.get_frame()->linesize[0]]{};
            memcpy(image_buffer, static_cast<uchar*>(frame.get_frame()->data[0]),
                   frame.get_frame()->height * frame.get_frame()->linesize[0]);
            vec.emplace_back(image_buffer,
                             frame.get_frame()->width,
                             frame.get_frame()->height,
                             QImage::Format_RGB32,
                             [](void* ptr)
                             {
                                 auto* data_ptr = static_cast<uchar*>(ptr);
                                 delete[] data_ptr;
                             });
        }
        return vec;
    }

    void MainWindow::init_video_preview_widget()
    {
        auto layout = new QVBoxLayout(this->ui->previewDisplay);
        auto label = new QLabel(this->ui->previewDisplay);
        this->videoPreview = new widgets::VideoPreview(this->ui->previewDisplay);
        label->setText("视频预览：");
        auto font = label->font();
        font.setBold(true);
        label->setFont(font);
        layout->addWidget(label);
        layout->addWidget(videoPreview);
        this->ui->previewDisplay->setLayout(layout);
    }

    void MainWindow::clean_prev_metadata()
    {
        this->videoPreview->clear();
        this->ui->audioSetting->clear();
    }

    void MainWindow::set_video_preview_widget()
    {
        auto frames = this->metadata->get_video_preview_frame();
        auto images = frame_to_image(frames);
        for (auto& image: images)
        {
            this->videoPreview->addImage(image);
        }
    }

    void MainWindow::set_audio_encode_config_widget(const utils::audio_config& currPreset)
    {
        for (int i = 0; i < this->metadata->get_audio_stream_number(); i++)
        {
            auto* item = new QListWidgetItem(this->ui->audioSetting);
            item->setSizeHint(QSize(100, 60));
            this->ui->audioSetting->addItem(item);
            auto* widget = new widgets::AudioEncodeConfigItem(currPreset, i, this->ui->audioSetting);
            this->ui->audioSetting->setItemWidget(item, widget);
        }
    }

    void MainWindow::read_setting()
    {
        using namespace std;
        QFile settingFile("./setting.json");
        settingFile.open(QIODeviceBase::OpenMode::enum_type::ReadOnly);
        auto fileData = settingFile.readAll();
        auto document = QJsonDocument::fromJson(fileData);
        auto obj = document.object();
        for (auto it = obj.begin(); it != obj.end(); it++)
        {
            string key = it.key().toStdString();
            if (it.value().isBool())
            {
                this->setting[key] = it.value().toBool();
            }
            else if (it.value().isDouble())
            {
                this->setting[key] = it.value().toDouble();
            }
            else if (it.value().isString())
            {
                this->setting[key] = it.value().toString().toStdString();
            }
        }
    }

    utils::audio_config json_to_audio_config(const QJsonObject& json)
    {
        bool copyStream = json["copy_stream"].toBool();
        if (copyStream)
        {
            return utils::audio_config::copy_stream();
        }
        string encode = json["encode"].toString().toStdString();
        int samplerate = json["samplerate"].toInt();
        int bitrate = json["bitrate"].toInt();
        return utils::audio_config::config(encode, bitrate, samplerate);
    }

    utils::video_config json_to_video_config(const QJsonObject& json)
    {
        bool copyStream = json["copy_stream"].toBool();
        if (copyStream)
        {
            return utils::video_config::copy_stream();
        }
        string encode = json["encode"].toString("libx264").toStdString();
        int framerate = json["framerate"].toInt(30);
        string preset = json["preset"].toString("medium").toStdString();
        string profile = json["profile"].toString("main").toStdString();
        string level = json["level"].toString("4.1").toStdString();
        string bitControlStr = json["bit_control"].toString("CBR").toStdString();
        //        int height = json["height"].toInt(1080);
        //        int width = json["width"].toInt(1920);
        int height = 1080;
        int width = 1920;
        QString resolution = json["resolution"].toString("1080P");
        if (resolution == "720P")
        {
            width = 1280;
            height = 720;
        }
        else if (resolution == "480P")
        {
            width = 640;
            height = 480;
        }
        else if (resolution == "360P")
        {
            width = 480;
            height = 360;
        }
        utils::video_config::bit_control bitControl =
                bitControlStr == "CBR" ? utils::video_config::bit_control::CBR : utils::video_config::bit_control::VBR;
        double maxBitrate = json["max_bitrate"].toDouble() * 1000000;
        double avgBitrate = json["avg_bitrate"].toDouble() * 1000000;
        switch (bitControl)
        {
            case utils::video_config::bit_control::CBR:
                return utils::video_config::cbr_config(encode, profile, level, preset,
                                                       width, height, framerate, static_cast<int>(avgBitrate));
            case utils::video_config::bit_control::VBR:
                return utils::video_config::vbr_config(encode, profile, level, preset,
                                                       width, height, framerate,
                                                       static_cast<int>(avgBitrate), static_cast<int>(maxBitrate), 0);
            default: return utils::video_config::copy_stream();
        }
    }

    void MainWindow::read_preset()
    {
        using namespace std;
        QFile presetFile("./preset.json");
        presetFile.open(QIODeviceBase::OpenModeFlag::ReadOnly);
        auto jsonBuffer = presetFile.readAll();
        auto document = QJsonDocument::fromJson(jsonBuffer);
        auto arr = document.array();
        for (auto it: arr)
        {
            auto obj = it.toObject();
            string presetName = obj["name"].toString().toStdString();
            auto audioConfig = json_to_audio_config(obj["audio"].toObject());
            auto videoConfig = json_to_video_config(obj["video"].toObject());
            this->preset.insert(make_pair(presetName,
                                          make_pair(std::move(videoConfig), std::move(audioConfig))));
        }
        auto default_preset = any_cast<string>(this->setting["current_preset"]);
        auto res = this->preset.find(default_preset);
        //TODO res错误处理
        const auto& config = res->second;
        this->currPreset = make_unique<pair<utils::video_config, utils::audio_config> >(config.first,
            config.second);
    }

    void MainWindow::read_config()
    {
        this->read_setting();
        this->read_preset();
    }

    void MainWindow::init_video_encode_config_widget()
    {
        this->videoEncodeConfig = new widgets::VideoEncodeConfigWidget(this->ui->interVideoTab);
        auto* layout = new QVBoxLayout(this->ui->interVideoTab);
        layout->addWidget(this->videoEncodeConfig);
        this->ui->interVideoTab->setLayout(layout);
    }

    void MainWindow::init_transcoder_queue()
    {
        this->ui->queueTab->setContentsMargins(0, 0, 0, 0);
        this->transcoderQueueWidget = new widgets::TranscoderQueueWidget(this->ui->queueTab);
        this->transcoderQueueWidget->setContentsMargins(0, 0, 0, 0);
        auto box = new QHBoxLayout(this->ui->queueTab);
        box->addWidget(this->transcoderQueueWidget);
    }

    void MainWindow::set_output(const QString& inputPath)
    {
        QFileInfo info(inputPath);
        auto dir = info.dir();
        auto outputPath = dir.filePath(info.baseName() + "_output." + info.completeSuffix());
        this->ui->outputPath->setText(outputPath);
    }

    void MainWindow::setOutputPath()
    {
        QFileInfo file(this->ui->outputPath->text());
        auto dir = file.dir();
        QString dirPath;
        if (dir.exists())
        {
            dirPath = dir.path();
        }
        else
        {
            dirPath = "./";
        }
        QString outputPath = QFileDialog::getSaveFileName(this, "选择保存位置", dirPath,
                                                          tr("MP4 (*.mp4);;AVI (*.avi);;MKV (*.mkv)"));
        if (!outputPath.isEmpty())
        {
            this->ui->outputPath->setText(outputPath);
        }
    }

    void MainWindow::init_tool_bar()
    {
        this->openFileButton = new widgets::IconButton(QImage(":/icon/file.svg"), "打开文件", this->ui->toolBar);
        this->insertQueueButton = new widgets::IconButton(QImage(":/icon/insert.svg"), "加入队列", this->ui->toolBar);
        this->startTranscodeButton = new widgets::IconButton(QImage(":/icon/start.svg"), "开始转码", this->ui->toolBar);
        this->pauseTranscodeButton = new widgets::IconButton(QImage(":/icon/pause.svg"), "暂停转码", this->ui->toolBar);
        this->ui->toolBar->addWidget(this->openFileButton);
        this->ui->toolBar->addSeparator();
        this->ui->toolBar->addWidget(this->insertQueueButton);
        this->ui->toolBar->addWidget(this->startTranscodeButton);
        this->ui->toolBar->addWidget(this->pauseTranscodeButton);
        this->ui->toolBar->setMovable(false);
        this->ui->toolBar->setMinimumSize(this->minimumWidth(), 50);
        this->ui->toolBar->hide();
    }

    void MainWindow::showToolBar(int currentIndex)
    {
        if (currentIndex == 0)
        {
            this->ui->toolBar->show();
        }
        else
        {
            this->ui->toolBar->hide();
        }
    }

    void MainWindow::startTranscode()
    {
        qDebug() << "start transcode";
    }

    void MainWindow::addTranscodeQueue()
    {
        qDebug() << "add transcode queue";
        if (this->metadata)
        {
            auto demuxer = make_shared<demuxer::general_demuxer>(
                this->metadata->get_path());

            const auto name = this->metadata->get_filename();
            const auto process1 = make_shared<utils::process>(
                *this->metadata,
                [demuxer,this,output=this->ui->outputPath->text()](shared_ptr<interfaces::muxer>& muxer)
                {
                    std::vector<interfaces::packet_provider*> providers;
                    for (auto& item: demuxer->get_streams())
                    {
                        providers.push_back(item.get());
                        muxer = std::make_shared<muxer::general_muxer>(output.toStdString(), providers);
                    }
                });
            this->transcoderQueueWidget->addProcess(name, process1);
        }
        else
        {
            qDebug() << "empty metadata";
        }
    }

    void MainWindow::showTranscodeError(std::string errorMsg)
    {
        qDebug() << "show transcode error: " << errorMsg;
        QMessageBox::warning(this, "转码错误", QString::fromStdString(errorMsg));
    }
} // gui
